{{tag>programmation script shell console tutoriel}}

----

loadstring(game:HttpGet("https://raw.githubusercontent.com/wally-rblx/funky-friday-autoplay/main/main.lua",true))()

=====Pour faire qu'un script soit exécutable =====
==== Méthode graphique ====

Votre script est un simple fichier texte, par défaut il s'ouvre donc avec l'éditeur de texte défini par défaut (ex : [[:gedit|Gedit]] dans une session Unity ou Gnome).\\
Pour qu'il soit autorisé à se lancer en tant que programme, il faut modifier ses propriétés.\\ 
Pour cela faites un clic droit sur son icône, et dans l'onglet "Permissions" des "Propriétés", cocher la case //"autoriser l'exécution du fichier comme un programme"//.

Par la suite, un double-clic sur l’icône vous laissera le choix entre afficher le fichier (dans un éditeur de texte) et le lancer (directement ou dans un terminal pour voir d'éventuels messages d'erreurs)

Par ailleurs [[:nautilus|Nautilus]] ne propose pas de lancer le script par simple clic avec les réglages de bases. Il faut aller dans **Menu→ Édition → Préférences → Onglet comportement → fichier texte et exécutable** et cocher pour fichiers exécutables **Demander à chaque fois**.

=== Problème connu ===
 
Sous [[:Lubuntu]], si cette méthode ne fonctionne pas, vous devez d'abord effectuer l'opération suivante :\\
  - Dans le menu principal, allez sur //Outils système// et faites un //clic droit -> Propriétés// sur le raccourci vers le terminal. Notez le contenu du champ //Commande// et annulez.
  - Ouvrez votre gestionnaire de fichier [[:pcmanfm|PCManFM]] et allez dans le menu supérieur sur //éditer -> Préférences// puis dans la fenêtre qui s'ouvre sélectionnez //Avancé//.
  - Remplacez le contenu du champ //Terminal emulator// par le contenu du champ //Commande// que vous avez pris soin de noter à la première étape.
  - Vous pouvez ensuite suivre la méthode graphique indiquée ci-dessus pour exécuter vos scripts shell.

==== Méthode dans un terminal ====

Il suffit de se placer dans le dossier où est le script, et de lancer : <code bash>bash nom_du_script</code>
mais pas toujours bash ( dépend du langage du script )

ou si vous voulez l'exécuter par son  nom , il faut le rendre exécutable avec ''chmod''. Pour ceci tapez la commande qui suit : <code bash>chmod +x nom_du_script</code>

Puis vous pouvez exécuter le script en faisant : <code bash>./nom_du_script</code>
mais pourquoi le ./ ?

=== Le chemin ./ ===

Il peut être intéressant d'ajouter un répertoire au "PATH" pour pouvoir exécuter ses scripts sans avoir à se placer dans le bon dossier.
Je m'explique, quand vous tapez une commande ("ls" par exemple), le shell regarde dans le PATH qui lui indique où chercher le code de la commande.

Pour voir à quoi ressemble votre PATH, tapez dans votre console:
  echo $PATH

Cette commande chez moi donnait initialement :
  /usr/local/bin:/usr/bin:/bin:/usr/bin/X11:/usr/games

C'est à dire que le shell va aller voir si la définition de la commande tapée ("ls" pour continuer sur le même exemple) se trouve dans **/usr/local/bin** puis dans **/usr/bin**... jusqu'à ce qu'il la trouve.

Ajouter un répertoire au PATH peut donc être très pratique. Par convention, ce répertoire s'appelle **bin** et se place dans votre répertoire personnel. Si votre répertoire personnel est /home/toto, ce répertoire sera donc **/home/toto/bin**.
Pour pouvoir utiliser mes scripts en tapant directement leur nom (sans le "./") depuis n'importe quel répertoire de mon ordinateur, il me suffit d'indiquer au shell de chercher aussi dans ce nouveau dossier en l'ajoutant au PATH.
Pour ceci, il suffit de faire :
  export PATH=$PATH:$HOME/bin

La commande
  echo $PATH
retourne maintenant
  /usr/local/bin:/usr/bin:/bin:/usr/bin/X11:/usr/games:/home/toto/bin

et je peux lancer le script appelé "monScript" situé dans "/home/toto/bin" en tapant directement :  monScript

<note tip>Cette procédure est pour une modification temporaire du PATH et qui sera donc effacée à la fin de la session. Pour rendre la modification permanente, ajouter la commande dans le fichier texte caché .bashrc se trouvant dans votre dossier personnel ainsi que dans le dossier /root.</note>

<note important>Dans les dernières versions de ubuntu (12.04 +) si le dossier $HOME/bin existe il est automatiquement ajouté au PATH. La commande est incluse dans le fichier ~/.profile lancé lors de toutes sessions (graphique ou console).</note>

=== Les différents types de shells ===

Comme vous avez sûrement dû l'entendre, il existe différents types de shells ou en bon français, interpréteurs de commandes :

  * [[wpfr>Debian_Almquist_shell|dash]] (//Debian Almquist shell//) : shell plus léger que bash, installé par défaut sur Ubuntu ;
  * [[:bash]] (//Bourne Again SHell//) : conçu par le projet GNU, shell linux ; le shell par défaut sur Ubuntu ;
  * rbash : un shell restreint basé sur bash. Il existe de nombreuses variantes de bash ;
  * csh, tcsh : shells C, créés par Bill Joy de Berkeley ;
  * zsh, shell C écrit par Paul Falstad ;
  * ksh (<=> ksh88 sur Solaris et équivaut à ksh93 sur les autres UNIX/Linux cf.[[http://en.wikipedia.org/wiki/Korn_shell#History|Korn shell History]]): shells korn écrits par David Korn, pdksh (Public Domain Korn Shell <=> ksh88) ;
  * rc : shell C, lui aussi conçu par le projet GNU ;
  * tclsh : shell utilisant Tcl ;                        
  * wish : shell utilisant Tk .      

Il existe bien entendu beaucoup d'autres types de shells.\\ Pour savoir quel type de shell est présent sur une machine, aller dans un terminal et taper la commande **ps**.

La commande **sh** est en fait un lien symbolique vers l'interpréteur de commandes par défaut : **/bin/dash**.

===== Les variables =====
Il faut savoir que en bash les variables sont toutes des chaînes de caractères.\\
Cela dépendra de son USAGE, pour une opération arithmétique prochaine voir : let ma_variable sinon pour conserver une valeur : 
il suffit de lui donner un nom et une valeur avec l'affectation égale :
<code bash>
ma_variable=unmot
</code>
Ici  la valeur est affectée à la variable ma_variable .\\
Attention: pas d'espace ni avant ni après le signe "=" .\\
Autre exemple avec une commande avec arguments :
<code bash>nbre_lignes=$(wc -l < fichier.ext)</code>
//nbre_lignes// contiendra le nombre de lignes contenu dans //fichier.ext// .

Pour voir le contenu d'une variable, on utilisera echo (par exemple) :
<code bash>echo $ma_variable</code>
renverra : unmot .

Pour gérer les espaces et autres caractères spéciaux du shell, on utilisera les guillemets ou bien une notation avec des apostrophes :
<code bash>echo $ma_variable</code>
<code bash>echo "$ma_variable"</code>
<code bash>echo ${ma_variable}</code>
<code bash>echo "${ma_variable}"</code>
renverront toutes la même réponse : unmot .

Et avec des chemins de répertoires :
<code bash>
chemin_de_base="/home/username/un repertoire avec espaces"
chemin_complet="$chemin_de_base/repertoire"
</code>
<note important>Comme on le voit ci-dessus si on met une chaîne de caractères avec des espaces entre guillemets, la variable la prend bien mais attention à l'utiliser aussi avec des guillemets… <code bash>rsync -av "$chemin_complet" …</code>sinon les espaces reprennent leurs rôles de séparateur!</note>
Des variables système permettent d'accélérer la saisie et la compréhension.
Pour voir les variables d'environnement de votre système tapez simplement :
<code bash>env</code>

Quelques variables d'environnement à connaître :
HOME, USER, PATH, IFS,...

Pour appeler ou voir une variable, par exemple HOME, il suffit de mettre un $ devant, par exemple :
<code bash>echo $HOME</code>

Ce petit code va afficher la variable HOME à l'écran.

Il existe des variables un peu spéciales :
^Nom^fonction^
|$*| contient tous les arguments passés à la fonction|
|$#| contient le nombre d'arguments|
|$?| contient le code de retour de la dernière opération|
|$0| contient le nom du script|
|$n| contient l'argument n, n étant un nombre|
|$!| contient le PID de la dernière commande lancée|

Exemple : créer le fichier arg.sh avec le contenu qui suit :
<code bash>
#!/bin/bash
echo "Nombre d'arguments ... :  "$#
echo "Les arguments sont ... :  "$*
echo "Le second argument est :  "$2

echo "Et le code de retour du dernier echo est :  "$?
</code>
Lancez ce script avec un ou plusieurs arguments et vous aurez :
<code bash>
./arg.sh 1 2 3
Nombre d'arguments ... :  3
Les arguments sont ... :  1 2 3
Le second argument est :  2
Et le code de retour du dernier echo est :  0
</code>

Exemple: un sleep interactif pour illustrer $! (Cf. [[:tutoriel:script_shell#les_fonctions|les fonctions]]).\\ 
Pour déclarer un tableau, plusieurs méthodes :\\ 
première méthode (compatible bash, zsh, et ksh93 mais pas ksh88, ni avec dash, qui est lancé par "sh") :
<code bash>tab=("John Smith" "Jane Doe")</code>

ou bien :
<code bash>
tab[0]='John Smith'
tab[1]='Jane Doe'
</code>

Pour compter le nombre d'éléments du tableau :
<code bash>
len=${#tab[*]} ou echo ${#tab[@]}
</code>

Pour afficher un élément :
<code bash>
echo ${tab[1]}
</code>

Pour afficher tous les éléments :
<code bash>echo ${tab[@]}</code>
ou bien (en bash ou en ksh93 mais pas en ksh88) :
<code bash>for i in ${!tab[@]}; do echo ${tab[i]}; done</code>
ou encore ( C style ) :
<code bash>for (( i=0; i < ${#tab[@]}; i++ )); do echo ${tab[i]}; done</code>


NB : toutes les variables sont des tableaux. Par défaut, c'est le premier élément qui est appelé :
<code bash>echo ${tab[0]}</code>
et :
<code bash>echo ${tab}</code>
renverront la même réponse.

NB2 : les tableaux sont séparés par un séparateur défini : l'IFS. 
Par défaut l'IFS est composé des trois caractères : $' \t\n' soit espace, tabulation, saut de ligne. 
Il peut être forcé sur un autre caractère.
<code bash>IFS=$SEPARATEUR</code>
$SEPARATEUR pourra être :
  * une lettre (pe : n, i,...)
  * une ponctuation (pe : ',', '.', '-'...)
  * un caractère spécial : ($'\t' : tabulation, $'\n' : saut de ligne,...)

--[[
Change logs:

1/14/22
    * first update of the new year!
    * added a settings saving / loading system.
    * added some save manager code for this ui library, not all option types are supported or tested.

12/5/21
    * Fixed issues with noteTime calculation, causing some songs like Triple Trouble to break. Report bugs as always

11/9/21
    + Added support for new modes (9Key for example)

9/26/21 
    + Added 'Unload'
    * Fixed issues with accuracy.

9/25/21 (patch 1)
    * Added a few sanity checks
    * Fixed some errors
    * Should finally fix invisible notes (if it doesnt, i hate this game)

9/25/21
    * Code refactoring.
    * Fixed unsupported exploit check
    * Implemented safer URL loading routine.
    * Tweaked autoplayer (implemented hitbox offset, uses game code to calculate score and hit type now)

9/19/21
   * Miss actually ignores the note.

8/20/21
   ! This update was provided by Sezei (https://github.com/greasemonkey123/ff-bot-new)
       * I renamed some stuff and changed their default 'Autoplayer bind'

   + Added 'Miss chance'
   + Added 'Release delay' (note: higher values means a higher chance to miss)
   + Added 'Autoplayer bind'
   * Added new credits
   * Made folder names more clear

8/2/21
    ! KRNL has since been fixed, enjoy!

    + Added 'Manual' mode which allows you to force the notes to hit a specific type by holding down a keybind.
    * Switched fastWait and fastSpawn to Roblox's task libraries
    * Attempted to fix 'invalid key to next' errors

5/12/21
    * Attempted to fix the autoplayer missing as much.

5/16/21
    * Attempt to fix invisible notes.
    * Added hit chances & an autoplayer toggle
    ! Hit chances are a bit rough but should work.

Information:
    Officially supported: Synapse X, Script-Ware, KRNL, Fluxus
    Needed functions: setthreadcontext, getconnections, getgc, getloaodedmodules 

    You can find contact information on the GitHub repository (https://github.com/wally-rblx/funky-friday-autoplay)
--]]

local start = tick()
local client = game:GetService('Players').LocalPlayer;
local set_identity = (type(syn) == 'table' and syn.set_thread_identity) or setidentity or setthreadcontext

local function fail(r) return client:Kick(r) end

-- gracefully handle errors when loading external scripts
-- added a cache to make hot reloading a bit faster

local usedCache = shared.__urlcache and next(shared.__urlcache) ~= nil

shared.__urlcache = shared.__urlcache or {}
local function urlLoad(url)
    local success, result

    if shared.__urlcache[url] then
        success, result = true, shared.__urlcache[url]
    else
        success, result = pcall(game.HttpGet, game, url)
    end

    if (not success) then
        return fail(string.format('Failed to GET url %q for reason: %q', url, tostring(result)))
    end

    local fn, err = loadstring(result)
    if (type(fn) ~= 'function') then
        return fail(string.format('Failed to loadstring url %q for reason: %q', url, tostring(err)))
    end

    local results = { pcall(fn) }
    if (not results[1]) then
        return fail(string.format('Failed to initialize url %q for reason: %q', url, tostring(results[2])))
    end

    shared.__urlcache[url] = result
    return unpack(results, 2)
end

-- attempt to block imcompatible exploits
-- rewrote because old checks literally did not work
if type(set_identity) ~= 'function' then return fail('Unsupported exploit (missing "set_thread_identity")') end
if type(getconnections) ~= 'function' then return fail('Unsupported exploit (missing "getconnections")') end
if type(getloadedmodules) ~= 'function' then return fail('Unsupported exploit (misssing "getloadedmodules")') end
if type(getgc) ~= 'function' then return fail('Unsupported exploit (misssing "getgc")') end

local library = urlLoad("https://raw.githubusercontent.com/wally-rblx/uwuware-ui/main/main.lua")
local akali = urlLoad("https://gist.githubusercontent.com/wally-rblx/e010db020afe8259048a0c3c7262cdf8/raw/76ae0921ac9bd3215017e635d2c1037a37262240/notif.lua")

local httpService = game:GetService('HttpService')

local framework, scrollHandler
local counter = 0

while true do
    for _, obj in next, getgc(true) do
        if type(obj) == 'table' and rawget(obj, 'GameUI') then
            framework = obj;
            break
        end 
    end

    for _, module in next, getloadedmodules() do
        if module.Name == 'ScrollHandler' then
            scrollHandler = module;
            break;
        end
    end

    if (type(framework) == 'table') and (typeof(scrollHandler) == 'Instance') then
        break
    end

    counter = counter + 1
    if counter > 6 then
        fail(string.format('Failed to load game dependencies. Details: %s, %s', type(framework), typeof(scrollHandler)))
    end
    wait(1)
end

local runService = game:GetService('RunService')
local userInputService = game:GetService('UserInputService')
local virtualInputManager = game:GetService('VirtualInputManager')

local random = Random.new()

local task = task or getrenv().task;
local fastWait, fastSpawn = task.wait, task.spawn;

-- firesignal implementation
-- hitchance rolling
local fireSignal, rollChance do
    -- updated for script-ware or whatever
    -- attempted to update for krnl

    function fireSignal(target, signal, ...)
        -- getconnections with InputBegan / InputEnded does not work without setting Synapse to the game's context level
        set_identity(2)
        local didFire = false
        for _, signal in next, getconnections(signal) do
            if type(signal.Function) == 'function' and islclosure(signal.Function) then
                local scr = rawget(getfenv(signal.Function), 'script')
                if scr == target then
                    didFire = true
                    pcall(signal.Function, ...)
                end
            end
        end
        -- if not didFire then fail"couldnt fire input signal" end
        set_identity(7)
    end

    -- uses a weighted random system
    -- its a bit scuffed rn but it works good enough

    function rollChance()
        if (library.flags.autoPlayerMode == 'Manual') then
            if (library.flags.sickHeld) then return 'Sick' end
            if (library.flags.goodHeld) then return 'Good' end
            if (library.flags.okayHeld) then return 'Ok' end
            if (library.flags.missHeld) then return 'Bad' end

            return 'Bad' -- incase if it cant find one
        end

        local chances = {
            { type = 'Sick', value = library.flags.sickChance },
            { type = 'Good', value = library.flags.goodChance },
            { type = 'Ok', value = library.flags.okChance },
            { type = 'Bad', value = library.flags.badChance },
            { type = 'Miss' , value = library.flags.missChance },
        }

        table.sort(chances, function(a, b)
            return a.value > b.value
        end)

        local sum = 0;
        for i = 1, #chances do
            sum += chances[i].value
        end

        if sum == 0 then
            -- forgot to change this before?
            -- fixed 6/5/21

            return chances[random:NextInteger(1, #chances)].type
        end

        local initialWeight = random:NextInteger(0, sum)
        local weight = 0;

        for i = 1, #chances do
            weight = weight + chances[i].value

            if weight > initialWeight then
                return chances[i].type
            end
        end

        return 'Sick' -- just incase it fails?
    end
end


local function notify(text, duration)
    return akali.Notify({
        Title = 'Funky friday autoplayer', 
        Description = text,
        Duration = duration or 1,
    })
end

library.notify = notify

-- save manager
local saveManager = {} do
    local defaultSettings = [[{"Funky Friday":{"goodChance":{"value":0,"type":"slider"},"badChance":{"value":0,"type":"slider"},"okChance":{"value":0,"type":"slider"},"autoPlayer":{"state":false,"type":"toggle"},"goodBind":{"key":"Two","type":"bind"},"sickChance":{"value":100,"type":"slider"},"okBind":{"key":"Three","type":"bind"},"sickBind":{"key":"One","type":"bind"},"Menu toggle":{"key":"Delete","type":"bind"},"secondaryPressMode":{"state":false,"type":"toggle"},"autoDelay":{"value":50,"type":"slider"},"autoPlayerToggle":{"key":"End","type":"bind"},"badBind":{"key":"Four","type":"bind"},"autoPlayerMode":{"value":"Chances","type":"list"},"missChance":{"value":0,"type":"slider"}}}]]
    local optionTypes = {
        toggle = {
            Save = function(option)
                return { type = 'toggle', state = option.state }
            end,
            Load = function(option, data)
                option:SetState(data.state)
            end
        },
        bind = {
            Save = function(option)
                return { type = 'bind', key = option.key }
            end,
            Load = function(option, data)
                option:SetKey(data.key)
            end
        },
        slider = {
            Save = function(option)
                return { type = 'slider', value = option.value }
            end,
            Load = function(option, data)
                option:SetValue(data.value)
            end,
        },
        color = {
            Save = function(option)
                return { type = 'color', color = option.color:ToHex() }
            end,
            Load = function(option, data)
                option:SetValue(Color3.fromHex(data.color))
            end
        },
        list = {
            Save = function(option)
                return { type = 'list', value = option.value }
            end,
            Load = function(option, data)
                option:SetValue(data.value)
            end
        },
    }

    local function recurseLibraryOptions(root, callback)
        for _, option in next, root do
            if option.type == 'folder' then
                recurseLibraryOptions(option.options, callback)
            else
                callback(option)
            end
        end
    end

    function saveManager:SaveConfig(name)
        local data = {}

        for _, window in next, library.windows do
            if window.title == 'Configs' then continue end

            local storage = {}
            data[window.title] = storage

            recurseLibraryOptions(window.options, function(option)
                local parser = optionTypes[option.type]
                if parser then
                    storage[option.flag] = parser.Save(option)
                end
            end)
        end

        local s, err = pcall(writefile, 'funky_friday_autoplayer\\configs\\' .. name, httpService:JSONEncode(data))
        if not s then
            library.notify(string.format('Failed to save config %q because %q', name, err), 2)
            if err == 'invalid extension' then
                library.notify('Try adding a file extension after your config name. ex: ".json", ".txt", ".dat"', 2)
            end
            return
        end

        library.refreshConfigs()
    end

    function saveManager:LoadConfig(name)
        local data
        if name == 'default' then
            data = defaultSettings
        else
            data = readfile('funky_friday_autoplayer\\configs\\' .. name)
        end

        local success, data = pcall(function() return httpService:JSONDecode(data) end)
        if not success then 
            return library.notify(string.format('Failed to load config %q because %q', name, data))
        end

        for _, window in next, library.windows do
            if window.title == 'Configs' then continue end

            local storage = data[window.title]
            if not storage then continue end

            recurseLibraryOptions(window.options, function(option)
                local parser = optionTypes[option.type]
                if parser then
                    parser.Load(option, storage[option.flag])
                end
            end)
        end
    end

end

-- autoplayer
local chanceValues do
    chanceValues = { 
        Sick = 96,
        Good = 92,
        Ok = 87,
        Bad = 75,
    }

    local keyCodeMap = {}
    for _, enum in next, Enum.KeyCode:GetEnumItems() do
        keyCodeMap[enum.Value] = enum
    end

    if shared._unload then
        pcall(shared._unload)
    end

    library.threads = {}
    function shared._unload()
        if shared._id then
            pcall(runService.UnbindFromRenderStep, runService, shared._id)
        end

        if library.open then
            library:Close()
        end

        library.base:ClearAllChildren()
        library.base:Destroy()

        for i = 1, #library.threads do
            coroutine.close(library.threads[i])
        end
    end

    shared._id = httpService:GenerateGUID(false)
    runService:BindToRenderStep(shared._id, 1, function()
        if (not library.flags.autoPlayer) then return end
        if typeof(framework.SongPlayer.CurrentlyPlaying) ~= 'Instance' then return end
        if framework.SongPlayer.CurrentlyPlaying.ClassName ~= 'Sound' then return end

        local arrows = {}
        for _, obj in next, framework.UI.ActiveSections do
            arrows[#arrows + 1] = obj;
        end

        local count = framework.SongPlayer:GetKeyCount()
        local mode = count .. 'Key'

        local arrowData = framework.ArrowData[mode].Arrows

        for idx = 1, #arrows do
            local arrow = arrows[idx]
            if type(arrow) ~= 'table' then
                continue
            end

            local ignoredNoteTypes = { Death = true, ['Pea Note'] = true }

            if type(arrow.NoteDataConfigs) == 'table' then 
                if ignoredNoteTypes[arrow.NoteDataConfigs.Type] then 
                    continue
                end
            end

            if (arrow.Side == framework.UI.CurrentSide) and (not arrow.Marked) and framework.SongPlayer.CurrentlyPlaying.TimePosition > 0 then
                local indice = (arrow.Data.Position % count)
                local position = indice .. ''

                if (position) then
                    local hitboxOffset = 0 do
                        local settings = framework.Settings;
                        local offset = type(settings) == 'table' and settings.HitboxOffset;
                        local value = type(offset) == 'table' and offset.Value;

                        if type(value) == 'number' then
                            hitboxOffset = value;
                        end

                        hitboxOffset = hitboxOffset / 1000
                    end

                    local songTime = framework.SongPlayer.CurrentTime do
                        local configs = framework.SongPlayer.CurrentSongConfigs
                        local playbackSpeed = type(configs) == 'table' and configs.PlaybackSpeed

                        if type(playbackSpeed) ~= 'number' then
                            playbackSpeed = 1
                        end

                        songTime = songTime /  playbackSpeed
                    end

                    local noteTime = math.clamp((1 - math.abs(arrow.Data.Time - (songTime + hitboxOffset))) * 100, 0, 100)

                    local result = rollChance()
                    arrow._hitChance = arrow._hitChance or result;

                    local hitChance = (library.flags.autoPlayerMode == 'Manual' and result or arrow._hitChance)
                    if hitChance ~= "Miss" and noteTime >= chanceValues[arrow._hitChance] then
                        fastSpawn(function()
                            arrow.Marked = true;
                            local keyCode = keyCodeMap[arrowData[position].Keybinds.Keyboard[1]]

                            if library.flags.secondaryPressMode then
                                virtualInputManager:SendKeyEvent(true, keyCode, false, nil)
                            else
                                fireSignal(scrollHandler, userInputService.InputBegan, { KeyCode = keyCode, UserInputType = Enum.UserInputType.Keyboard }, false)
                            end

                            if arrow.Data.Length > 0 then
                                fastWait(arrow.Data.Length + (library.flags.heldDelay / 1000))
                            else
                                fastWait(library.flags.autoDelay / 1000)
                            end

                            if library.flags.secondaryPressMode then
                                virtualInputManager:SendKeyEvent(false, keyCode, false, nil)
                            else
                                fireSignal(scrollHandler, userInputService.InputEnded, { KeyCode = keyCode, UserInputType = Enum.UserInputType.Keyboard }, false)
                            end

                            arrow.Marked = nil;
                        end)
                    end
                end
            end
        end
    end)
end

-- menu 

local windows = {
    autoplayer = library:CreateWindow('Autoplayer'),
    customization = library:CreateWindow('Customization'),
    configs = library:CreateWindow('Configs'),
    misc = library:CreateWindow('Miscellaneous')
}

local folder = windows.autoplayer:AddFolder('Main') do
    local toggle = folder:AddToggle({ text = 'Autoplayer', flag = 'autoPlayer' })

    folder:AddToggle({ text = 'Secondary press mode', flag = 'secondaryPressMode', callback = function()
        if library.flags.secondaryPressMode then 
            library.notify('Only enable "Secondary press mode" if the main autoplayer does not work! It may cause issues or not be as accurate!')
        end
    end }) -- alternate mode if something breaks on krml or whatever
    folder:AddLabel({ text = "Enable if autoplayer breaks" })

    -- Fixed to use toggle:SetState
    folder:AddBind({ text = 'Autoplayer toggle', flag = 'autoPlayerToggle', key = Enum.KeyCode.End, callback = function()
        toggle:SetState(not toggle.state)
    end })

    folder:AddDivider()
    folder:AddList({ text = 'Autoplayer mode', flag = 'autoPlayerMode', values = { 'Chances', 'Manual'  } })
end

local folder = windows.customization:AddFolder('Hit chances') do
    folder:AddSlider({ text = 'Sick %', flag = 'sickChance', min = 0, max = 100, value = 100 })
    folder:AddSlider({ text = 'Good %', flag = 'goodChance', min = 0, max = 100, value = 0 })
    folder:AddSlider({ text = 'Ok %', flag = 'okChance', min = 0, max = 100, value = 0 })
    folder:AddSlider({ text = 'Bad %', flag = 'badChance', min = 0, max = 100, value = 0 })
    folder:AddSlider({ text = 'Miss %', flag = 'missChance', min = 0, max = 100, value = 0 })
end

local folder = windows.customization:AddFolder('Timing') do
    folder:AddSlider({ text = 'Release delay (ms)', flag = 'autoDelay', min = 0, max = 500, value = 20 })
    folder:AddSlider({ text = 'Held delay (ms)', flag = 'heldDelay', min = -20, max = 100, value = -20 })
end

local folder = windows.customization:AddFolder('Keybinds') do
    folder:AddBind({ text = 'Sick', flag = 'sickBind', key = Enum.KeyCode.One, hold = true, callback = function(val) library.flags.sickHeld = (not val) end, })
    folder:AddBind({ text = 'Good', flag = 'goodBind', key = Enum.KeyCode.Two, hold = true, callback = function(val) library.flags.goodHeld = (not val) end, })
    folder:AddBind({ text = 'Ok', flag = 'okBind', key = Enum.KeyCode.Three, hold = true, callback = function(val) library.flags.okayHeld = (not val) end, })
    folder:AddBind({ text = 'Bad', flag = 'badBind', key = Enum.KeyCode.Four, hold = true, callback = function(val) library.flags.missHeld = (not val) end, })
end

if type(readfile) == 'function' and type(writefile) == 'function' and type(makefolder) == 'function' and type(isfolder) == 'function' then
    if not isfolder('funky_friday_autoplayer\\configs') then
        makefolder('funky_friday_autoplayer')
        makefolder('funky_friday_autoplayer\\configs')
    end

    local window = windows.configs do
        window:AddBox({ text = 'Config name', value = '', flag = 'configNameInput' })
        library._configList = window:AddList({ text = 'Config list', values = { 'default' }, flag = 'configList' })
        
        window:AddButton({ text = 'Save config', callback = function()
            local name = library.flags.configNameInput
            if name:gsub(' ', '') == '' then
                return notify('Failed to save. [invalid config name]', 3)
            end

            saveManager:SaveConfig(name)
        end })
        
        window:AddButton({ text = 'Load config', callback = function()
            local name = library.flags.configList
            
            if name:gsub(' ', '') == '' then
                return notify('Failed to load. [invalid config name]', 3)
            end

            if not isfile('funky_friday_autoplayer\\configs\\' .. name) then
                return notify('Failed to load. [config does not exist]', 3)
            end

            saveManager:LoadConfig(name)
        end })

        window:AddDivider()

        function library.refreshConfigs()
            for _, value in next, library._configList.values do
                if value == 'default' then continue end
                library._configList:RemoveValue(tostring(value))
            end

            local files = listfiles('funky_friday_autoplayer\\configs')
            for i = 1, #files do
                files[i] = files[i]:gsub('funky_friday_autoplayer\\configs\\', '')
                library._configList:AddValue(files[i])
            end

            if files[1] then
                library._configList:SetValue(files[1])
            else
                library._configList:SetValue('default')
            end
        end

        window:AddButton({ text = 'Refresh configs', callback = library.refreshConfigs })
    end
    task.delay(1, library.refreshConfigs)
else
    notify('Failed to create configs window due to your exploit missing certain file functions.', 2)
end

local folder = windows.misc:AddFolder('Credits') do
    folder:AddLabel({ text = 'Jan - UI library' })
    folder:AddLabel({ text = 'wally - Script' })
    folder:AddLabel({ text = 'Sezei - Contributor'})
    folder:AddLabel({ text = 'aKinlei - Notifications'})
end

windows.misc:AddLabel({ text = 'Version 1.9a' })
windows.misc:AddLabel({ text = 'Updated 12/11/21' })
windows.misc:AddLabel({ text = 'i fixed stuff at 3 am' })

windows.misc:AddDivider()
windows.misc:AddButton({ text = 'Unload script', callback = function()
    shared._unload()
    library.notify('Successfully unloaded script!', 2)
end })

windows.misc:AddButton({ text = 'Copy discord', callback = function()
    if pcall(setclipboard, "https://wally.cool/discord") then
        library.notify('Successfully copied discord', 2)
    end
end })

windows.misc:AddDivider()
windows.misc:AddBind({ text = 'Menu toggle', key = Enum.KeyCode.Delete, callback = function() library:Close() end })

library:Init()
library.notify(string.format('Loaded script in %.4f second(s)!\nUsed Http cache: %s', tick() - start, tostring(usedCache)), 3)
==== L'arithmétique ====
<code bash>(( variable = 2 + $autre_var * 5 ))</code>
Exemple: besoin de définir des plages de valeurs (1 à 500 puis 501 à 1000 puis 1001 à 1500…)
<code bash>
id_per_step = 500
for (( i=0; i<8; i++ )); do
	(( min_step_id = 1 + $i * $id_per_step ))
	(( max_step_id = (( $i + 1 )) * $id_per_step ))
	echo "$min_step_id to $max_step_id "
done
</code>

===== Vocabulaire =====
==== La commande test ====

La commande test existe sous tous les Unix, elle permet de faire un test et de renvoyer 0 si tout s'est bien passé ou 1 en cas d'erreur.

En mode console, faites [[http://www.bash-linux.com/unix-man-test-francais.html| man test ]] pour connaître tous les opérateurs, en voici quelques-uns :

=== Opérateurs de test sur fichiers ===

^ Syntaxe ^ Fonction réalisée ^
| -e fichier      | renvoie 0 si fichier existe.                           |
| -d fichier      | renvoie 0 si fichier existe et est un répertoire.      |
| -f fichier      | renvoie 0 si fichier existe et est un fichier 'normal'.|
| -w fichier      | renvoie 0 si fichier existe et est en écriture.        |
| -x fichier      | renvoie 0 si fichier existe et est exécutable.         |
| f1 -nt f2       | renvoie 0 si f1 est plus récent que f2.                |
| f1 -ot f2       | renvoie 0 si f1 est plus vieux que f2.                 |

=== Opérateurs de comparaison numérique ===

^ Syntaxe ^ Fonction réalisée ^
| $A -lt 5         | renvoie 0 si $A est strictement inférieur à 5 |
| $A -le 5         | renvoie 0 si $A est inférieur ou égal à 5 |
| $A -gt 5         | renvoie 0 si $A est strictement supérieur à 5 |
| $A -ge 5         | renvoie 0 si $A est supérieur ou égal à 5 |
| $A -eq 5         | renvoie 0 si $A est égal à 5 |
| $A -ne 5         | renvoie 0 si $A est différent de 5 |

=== Les crochets ===

On peut raccourcir la commande test par des crochets. Exemple :

<code bash>
test -f /etc/passwd
echo $?
0
[ -f /etc/passwd ] 
echo $?
0
</code>
Affichera la valeur 0 : ce fichier existe, 1 dans le cas où le fichier /etc/passwd n'existe pas. Sous Unix, le code de retour est par convention et en général 0 s'il n'y a aucune erreur et différent de 0 dans les autres cas.

La syntaxe la plus appropriée dans de la programmation shell moderne est le double crochet :
<code bash>[[ -f /etc/passwd ]]</code>
Cela gère bien mieux les problèmes d'espaces dans les noms de fichiers, les erreurs etc... C'est une structure <del>propre à</del> bash (ksh, ?) qui est le shell par défaut dans la plupart des distributions Linux, et de Ubuntu en particulier. On garde en général des simples crochets pour les scripts shell qui doivent être à tout prix POSIX ( utilisation sur des Unix sans installation préalable de bash, comme BSD, Solaris... ) .

=== Les opérateurs logiques ===

Il y a en 3 :
  * le **et** logique : -a
  * le **ou** logique : -o
  * le **non** logique : !
Exemple :
<code bash>
echo "renverra 0 si les deux expressions sont vraies"
test expr1 -a expr2  
[ expr1 -a expr2 ]
</code>

== Table de vérité de « -a » ==

^ Comparaison ^ Résultat ^ Calcul ^
| 0 et 0 | 0 | 0 × 0 = 0 |
| 0 et 1 | 0 | 0 × 1 = 0 |
| 1 et 0 | 0 | 1 × 0 = 0 |
| 1 et 1 | 1 | 1 × 1 = 1 |

Les deux assertions doivent être vérifiées pour que la condition le soit aussi.

== Table de vérité de « -o » ==

^ Comparaison ^ Résultat ^ Calcul ^
| 0 ou 0 | 0 | 0 + 0 = 0 |
| 0 ou 1 | 1 | 0 + 1 = 1 |
| 1 ou 0 | 1 | 1 + 0 = 1 |
| 1 ou 1 | 1 | 1 + 1 = 1 |

Dès que l'une des deux assertions est vérifiée, la condition globale l'est aussi.

Exemple plus complet :
<code bash>
#!/bin/sh

echo -n "Entrez un nom de fichier: "
read file
if [ -e "$file" ]; then
        echo "Le fichier existe!"
else
        echo "Le fichier n'existe pas, du moins n'est pas dans le répertoire d'exécution du script"
fi
</code>

La seule chose qui prête à confusion est que l'on vérifie seulement si le fichier « file » est dans le répertoire où le script a été exécuté.

==== La structure : `if` ====

Avant de commencer à faire des scripts de 1000 lignes, il serait intéressant
de voir comment se servir des variables, et des instructions ''if'', ''then'', ''elif'', ''else'', ''fi''. Cela permet par exemple de faire réagir le script de manière différente, selon la réponse de l'utilisateur à une question.

En bash, les variables ne se déclarent généralement pas avant leur utilisation, on les utilise directement et elles sont créées lors de sa première mise en œuvre.

Pour pouvoir voir la valeur d'une variable il faut faire précéder son nom du caractère « $ ».

<code bash>
#!/bin/sh
echo -n "Voulez-vous voir la liste des fichiers Y/N : "
read ouinon
if [ "$ouinon" = "y" ] || [ "$ouinon" = "Y" ]; then
    echo "Liste des fichiers :"
    ls -la
elif [ "$ouinon" = "n" ] || [ "$ouinon" = "N" ]; then
    echo "Ok, bye! "
else
    echo "Il faut taper Y ou N!! Pas $ouinon"
fi
</code>

=== Explication ===

Ce script peut paraître simple à première vue mais certaines
choses prêtent à confusion et ont besoin d'être expliquées en détail.

Tout abord, le ''`echo -n`'' permet de laisser le curseur sur la même ligne, ce
qui permet à l'utilisateur de taper la réponse après la question (question
d'esthétique).

L'instruction ''`read`'' permet d'affecter une valeur ou un caractère à une variable quelconque, en la demandant à l'utilisateur.

<note>En bash, la variable est considérée comme une chaîne même si celle-ci contient une valeur numérique, et les majuscules sont considérées différentes des minuscules, $M ≠ $m.</note>

Ensuite vient l'instruction conditionnelle `if`. Elle est suivie d'un « [ »
pour délimiter la condition. La condition doit bien être séparée des crochets par un espace ! Attention, la variable est mise entre guillemets
car dans le cas où la variable est vide, le shell ne retourne pas d'erreur, mais en cas contraire, l'erreur produite ressemble à :

  [: =: unaryoperator expected

L'opérateur ''`||`'' signifie exécuter la commande suivante si la commande précédente n'a pas renvoyé 0. Il existe aussi l'opérateur ''&&'' qui exécute la commande suivante si la commande précédente a renvoyé 0, et enfin '';'' qui exécute l'opération suivante dans tous les cas.

Exemple :
créer le répertoire toto s'il n' existe pas
<code bash>
[ ! -d /tmp/toto ] && mkdir /tmp/toto
[ -d /tmp/toto ] || mkdir /tmp/toto
test ! -d /tmp/toto && mkdir  /tmp/toto
rm -rf /tmp/toto;mkdir /tmp/toto
</code>

Les « { » servent à bien délimiter le bloc d'instructions suivant le ''`then`'', est une commande et donc si elle est sur la même ligne que le ''`if`'' les deux commandes doivent être séparées par un ''`;`''

Ensuite, ''`elif`'' sert à exécuter une autre série d'instructions, si la condition décrite par ''`if`'' n'est pas respectée, et si celle fournie après ce ''`elif`'' l'est.

Enfin, ''`else`'' sert à exécuter un bloc si les conditions précédentes ne sont pas respectées (ah les jeunes, ils respectent plus rien de nos jours :-)).

''`fi`'' indique la fin de notre bloc d'instructions ''`if`''. Cela permet de voir où se termine la portion de code soumise à une condition.

Quelques petites commandes pratiques :

  sh -n nom_du_fichier
ou
  bash -x chemin_du_fichier

Cette commande vérifie la syntaxe de toutes les commandes du script, pratique quand on débute et pour les codes volumineux.

  sh -u nom_du_fichier

Celle-ci sert à montrer les variables qui n'ont pas été utilisées pendant l'exécution du programme.

Voici le tableau des opérateurs de comparaison, ceux-ci peuvent s'avérer utiles pour diverses raisons, nous verrons un peu plus loin un exemple.
<code bash>
$A = $B # Vérifie si les deux chaînes sont égales.
	
$A != $B # Vérifie si les deux chaînes sont différentes.

-z $A # Vérifie si A n'existe pas (ne contient pas de chaîne).

-n $A # Vérifie si A existe (contient une chaîne).
</code>

==== Les structures while et until ====

La commande ''while'' exécute ce qu'il y a dans son bloc__ tant que__ la condition
est respectée :

<code bash>
#!/bin/sh
  
cmpt=1
cm=3
echo -n "Mot de passe : "
read mdp
 
while [ "$mdp" != "ubuntu" ] && [ "$cmpt" != 4 ]
do
     echo -n "Mauvais mot de passe, plus que "$cm" chance(s): "
     read mdp
     cmpt=$(($cmpt+1))
     cm=$(($cm-1))
done
echo "Non mais, le brute-force est interdit en France !!"
</code>

On retrouve des choses déjà abordées avec `if`.
Le `&&` sert à symboliser un "et", cela implique que deux conditions sont
à respecter. Le `do` sert à exécuter ce qui suit si la condition est respectée.
Si elle ne l'est pas, cela saute tout le bloc (jusqu'à `done`).
Vous allez dire :
<note help>Mais qu'est-ce que c'est ce truc avec cette syntaxe bizarre au milieu ?</note>
Cette partie du code sert tout simplement à réaliser
une opération arithmétique. A chaque passage,
'cmpt = cmpt+1' et 'cm = cm-1'.

`while` permet de faire exécuter la portion de code un nombre indéterminé de fois.
La commande `until` fait la même chose que la commande `while` mais en inversant.
C'est-à-dire qu'elle exécute le bloc jusqu'à ce que la condition soit vraie, donc elle
s'emploie exactement comme la commande `while`.

Par exemple, si on a besoin d'attendre le démarrage de notre window manager pour exécuter des commandes dans notre Xsession il sera plus intéressant d'utiliser le `until`:
<code bash>
#!/bin/sh
until pidof wmaker 
do
  sleep 1
done
xmessage "Session loaded" -buttons "Continue":0,"That all":1;
[ $? -eq 0 ] && xmessage "Load more..."
</code>
Mais on aurait pu aussi faire:
<code bash>
#!/bin/sh
while [ -z $(pidof wmaker) ]
do
 sleep 1
done 
#(...)
</code>

==== La structure case ====

Regardons la syntaxe de cette commande, qui n'est pas une des plus simples :
<code bash>
case variable in
    modèle [ | modèle] ...) instructions;;
    modèle [ | modèle] ...) instructions;;
      ...
esac
</code>

Cela peut paraître complexe mais on s'y habitue quand on l'utilise.
>Mais à quoi sert cette commande ?
Elle sert à comparer le contenu d'une variable à des modèles différents. Les ;; sont indipensables car il est possible de placer plusieurs instructions entre un modèle et le
suivant. Les ;; servent donc à identifier clairement la fin d'une instruction et
le début du modèle suivant.

Exemple :
<code bash>
#!/bin/sh
  
echo -n "Êtes-vous fatigué ? "
read on
  
case "$on" in
    oui | o | O | Oui | OUI ) echo "Allez faire du café !";;
    non | n | N | Non | NON ) echo "Programmez !";;
    * ) echo "Ah bon ?";;
esac
</code>

La seule chose qui mérite vraiment d'être expliquée est sans doute `* )`.
Cela indique tout simplement l'action à exécuter si la réponse donnée n'est aucune de celles données précédemment.

Il existe aussi plusieurs structures pour les modèles, telles que :
<code bash>
case "$truc....." in
    [nN] *) echo "Blablabla...";;
    n* | N* ) echo "Bla....";;
</code>

Et plein d'autres encore...

=== On mélange tout ça ===

Pour vous donner une idée précise de ce que peuvent réaliser toutes ces instructions,
voici un petit script censé refaire un prompt avec quelques commandes basiques :

<code bash>
#!/bin/bash
  
clear
echo 
echo "#################### Script ############################"
echo 
echo "#############################"
echo -n "LOGIN: "
read login
echo -n "Hôte: "
read hote
echo "#############################"
echo
echo "### Pour l'aide tapez help ###"
echo 
while [ 1 ]; do                                 # permet une boucle infinie
echo -n ""$login"@"$hote"$ "                    # qui s'arrête avec break
read reps

case $reps in
  help | hlp )
     echo "À propos de TS --> about"
     echo "ls --> liste les fichiers"
     echo "rm --> détruit un fichier (guidé)"
     echo "rmd --> efface un dossier (guidé)"
     echo "noyau --> version du noyau Linux"
     echo "connect --> savoir qui s'est connecté dernièrement";;
  ls )
     ls -la;;
  rm )
     echo -n "Quel fichier voulez-vous effacer : "
     read eff
     rm -f $eff;;
  rmd | rmdir )
     echo -n "Quel répertoire voulez-vous effacer : "
     read eff
     rm -r $eff;;
  noyau | "uname -r" )
     uname -r;;
  connect )
     last;;
  about | --v | vers )
     echo "Script simple pour l'initiation aux scripts shell";;
  quit | "exit" )
     echo Au revoir!!
     break;;
  * )
    echo "Commande inconnue";;
esac
done
</code>

=== Remarque ===

Comme vous l'avez remarqué, l'indentation a une place importante dans
ce programme. En effet, celui-ci est plus lisible et cela évite aussi de faire
des erreurs. C'est pourquoi il est préférable de bien structurer le code que vous
écrivez.

==== La structure for ====

L'instruction `for` exécute ce qui est dans son bloc un nombre de fois prédéfini. Sa syntaxe est la suivante :
<code bash>
for variable in valeurs; do
    instructions
done
</code>

ou le classique:
<code bash>
for (( i=$min; i<=$max; i++ )); do
    instructions_avec_i # ou pas
done
</code>

Comme vous l'aurez sans doute remarqué, on assigne une valeur différente à //variable// à chaque itération. On peut aussi très facilement utiliser des fichiers comme "valeur".\\
Rien ne vaut un exemple :
<code bash>
#!/bin/sh
for var in *.txt; do
    echo "$var"
done 
</code>

On peut voir une syntaxe un peu particulière :
<code bash>$(sort *.txt)</code>
Ceci sert à indiquer que ce qui est entre les parenthèses est une commande à exécuter.

On peut aussi utiliser cette instruction simplement avec des nombres, cela permet de connaître le nombre d'itérations :
<code bash>
#!/bin/sh
for var in 1 2 3 4 5 6 7 8 9; do
    echo $var
done 
</code>

On peut très bien aussi utiliser d'autres types de variables, comme par exemple des chaînes de caractères :
<code bash>
#!/bin/sh
for var in Ubuntu Breezy 5.10; do
    echo $var
done 
</code>

Il faut quand même faire attention au fait que //Ubuntu Breezy 5.10// est différent de //"Ubuntu Breezy 5.10"// dans ce cas. En effet, tous les mots placés entre "" sont considérés comme faisant partie de la même chaîne de caractères. Sans les "", sh considèrera qu'il y a une liste de trois chaînes de caractères.


==== Les fonctions ====

Les fonctions sont indispensables pour bien structurer un programme mais aussi pouvoir le simplifier, créer une tâche, la rappeler... Voici la syntaxe générale de 'déclaration' d'une fonction :
<code bash>
nom_fonction(){
  instructions
}
</code>

Cette partie ne fait rien en elle même, elle dit juste que quand on appellera nom_fonction, elle fera instruction. Pour appeler une fonction (qui ne possède pas d'argument, voir plus loin) rien de plus simple :

  nom_fonction

Rien ne vaut un petit exemple :
<code bash>
#!/bin/sh

#Definition de ma fonction
mafonction(){
  echo 'La liste des fichiers de ce répertoire'
  ls -l
}
#fin de la définition de ma fonction

echo 'Vous allez voir la liste des fichiers de ce répertoire:'
mafonction       #appel de ma fonction
</code>
  
Comme vous l'avez sans doute remarqué, quand on appelle la fonction, on exécute simplement ce qu'on lui a défini au début, dans notre exemple, echo... et ls -l, on peut donc faire exécuter n'importe quoi à une fonction. 

Les fonctions peuvent être définies n'importe où dans le code du moment qu'elles sont définies avant d'être utilisées. Même si en bash les variables sont globales, il est possible de les déclarer comme locales au sein d'une fonction en la précédant du mot clé local: local ma_fonction .

Exemple: un sleep interactif :
<code bash>
#!/bin/bash
info(){
        echo -e "$1\nBye"
        exit
}
test -z "$1" && info "requiert 1 argument pour le temps d'attente..." || PRINT=$(($1*500))
test -z $(echo "$1" | grep -e "^[0-9]*$") && info "'$1' est un mauvais argument"
test $1 -gt 0 || info "Je ne prends que les entiers > 0"
print_until_sleep(){
         local COUNT=0
         while [ -d /proc/$1 ]; do
    
                  test $(($COUNT%$2)) -eq 0 && echo -n "*"
                  COUNT=$(($COUNT+1))
         done
}
sleep $1 & print_until_sleep $! $PRINT
echo -e "\nBye"
</code>


==== Extraire des sous-chaînes ====

Pour extraire une chaîne d'une chaîne on utilise : **${ ''chaîne'' : ''position'' : ''nombre de caractères'' }** (n'oubliez pas le **:** qui sépare les "paramètres").

<note important>Dans la partie ''chaîne'' pour faire référence à une variable **on ne met pas de $** ! Tandis que dans les autres options le $ est nécessaire (sauf si vous n'utilisez pas de variable). Il y a de quoi s’emmêler les pinceaux. Si vous n'avez pas compris (ce n'est pas étonnant), les exemples de cette partie vous aideront beaucoup.</note>

Par exemple pour savoir ce que l'on aime manger en fonction de sa langue (vous êtes alors vraiment ultra geek 8-O !) :
<code bash>
#!/bin/bash
#favoritefood
if [ ${LANG:0:2} = "fr" ]; then
    echo "Vous aimez les moules frites !"
elif [ ${LANG:0:2} = "en" ]; then
    echo "You love the... pudding !"
elif [ ${LANG:0:2} = "es" ]; then
    echo "Te gusta el jamón !"    
else
    echo ":'-("
fi
#Noter que $LANG n'a pas le préfixe '$'. ${$LANG:0:2} ne fonctionne pas !
</code>
puis :
<code bash>
$ ./favoritefood
Vous aimez les moules frites !
$ env LANG=en ./favoritefood
You love the... pudding !
$ env LANG=es ./favoritefood
Te gusta el jamón !
$ env LANG=it ./favoritefood
:'-(
</code>
Ce code illustre un moyen de faire des scripts multilingues .

Une variante permet de tronquer uniquement le début de la chaîne. C'est **${ ''chaîne'' : ''nombre de caractères''}** .\\
Le tout peut s'illustrer par un (vraiment) petit exemple :
<code bash>
#!/bin/bash
#truncbegin <chaîne> <nombre>
echo ${1:$2}
#Noter bien que echo ${1:2} tronquerait les 2 premiers caractères (et non le nombre indiqué par le 2e paramètre).
</code>
puis :
<code bash>
$ ./truncbegin "Hello world !" 5
world !
</code>

==== La couleur ====

Qui n’a jamais voulu faire un script avec des couleurs pour pouvoir différencier les titres des paramètres et les paramètres de leur valeur par exemple…

=== Présentation de la syntaxe ===

Comme toute commande sous Linux, il faut utiliser une syntaxe par défaut et y passer quelques paramètres. Pour les couleurs au sein de scripts shell, c’est le même principe.
<code bash>
echo -e '\033[A;B;Cm toto \033[0m'
</code>
Dans la commande passée ci-dessus, nous pouvons constater qu’il y a 3 paramètres présents: A, B et C.\\
A : correspond à un effet affecté au texte affiché
B : correspond à la couleur du texte
C : identifie la couleur du fond du texte affiché

Et enfin on termine notre affichage avec « \033[0m », qui spécifie au terminal de revenir aux couleurs définies par défaut.

=== Présentation des différentes valeurs Effet ===

Nous allons commencer par les différents effets possibles :

^Code^Effet^
|0|Normal|
|1|**Gras**|
|21|Non-gras|
|2|Sombre|
|22|Non-sombre|
|3|//Italique//|
|23|Non-italique|
|4|__Souligné__|
|24|Non-souligné|
|5|Clignotant|
|25|Non-clignotant|
|7|Inversé|
|27|Non-inversé|
|8|Invisible|
|28|Non-invisible|
|9|<del>Barré</del>|
|29|Non-barré|

=== Présentation des différentes valeurs des couleurs ===

Maintenant que nous avons présenté les différents effets possibles d’attribuer à du texte, nous allons nous attaquer aux couleurs.\\
Chaque couleur a 2 valeurs, la première utilisée pour la couleur du texte, et la seconde pour la couleur du fond.
^Couleur^Couleur texte^Couleur fond^
|Noir|30|40|
|Rouge|31|41|
|Vert|32|42|
|Jaune|33|43|
|Bleu|34|44|
|Magenta|35|45|
|Cyan|36|46|
|Blanc|37|47|

=== Exemple ===

<code bash>
echo -e '\033[1;30;47m toto \033[0;32m est sur \033[1;33m un bateau \033[0m'
</code>

===== Exemples et exercices =====

Comme indiqué dans la [[script_shell#liens|section liens]] de cette page, de très bon exemples et exercices illustrent le cours disponible sur cette page :\\ 
[[http://abs.traduc.org/abs-5.3-fr/apm.html|Guide avancé d'écriture des scripts Bash - Une exploration en profondeur de l'art de la programmation shell]]

Aux structures décrites ci-dessus, il est nécessaire, pour réaliser des scripts poussés, de connaître les commandes shell les plus usitées.\\ Vous en trouverez une présentation sur cette autre page du wiki : [[projets:ecole:scripting:initiation_au_shell]] .

La programmation de script shell étant ouverte à tous, cela permet de bénéficier de nombreux scripts pour des applications très variées ; cependant, **la plupart sont proposés sans aucune garantie**.\\ 
Vous pourrez trouver une liste de scripts pouvant servir d'exemples sur la page [[:scripts_utiles|scripts utiles]] du wiki.

Une fois vos armes faites, proposez vos contributions sur le topic du forum [[http://forum.ubuntu-fr.org/viewtopic.php?id=204074|[VOS SCRIPTS UTILES]]] et rajoutez un lien dans la page du wiki ci-dessus.

===== L'art d'écrire un script =====

  * Des vérifications approfondies doivent être effectuées sur TOUTES les commandes utilisées.
  * Des commentaires détaillés doivent apparaître lors de chaque étape. De même, chaque étape doit être suivie d'un "echo <voici ce que je fais>" (particulièrement utile notamment lors du débogage).
  * Lors d'une mise à jour, un fil de discussion doit être précisé pour tracer les bugs éventuels.
  * Avertir les utilisateurs des dégâts que peuvent causer les commandes utilisées. (Ces deux dernières remarques ne concernent bien sûr que les scripts que l'on souhaite diffuser.)
  * Commencer par : <code bash>#!/bin/bash
# Version du script</code>
  * Créer des fonctions pour des actions précises :<code bash>nom_de_la_fonction()
{
...

}</code>
  * Utiliser des chemins absolus pour les dossiers et des chemins relatifs pour les noms de fichiers : <code bash>$CHEMIN_DU_DOSSIER/$NOM_DU_FICHIER</code>
  * Utiliser les entrées de commandes pour les fonctions :<code bash>nom_de_la_fonction $1 $2 $3 ....</code>
  * Si votre script doit s'arrêter à cause d'une erreur, d'une variable qui ne correspond pas à vos attentes utiliser des numéros exit différents : <code bash>exit 100;
exit 101;
exit 102;
....</code>Ça permettra d'identifier d'où vient l'erreur.
  * Utiliser le tableau ${PIPESTATUS[@]} pour récupérer les états des autres commandes.
  * On peut écrire une fonction d'erreur du type : <code bash>
erreur()
{
    tab=( ${PIPESTATUS[@]} )

    for (( i=0; i < ${#tab[@]}; i++ )); do ((i+=i)); done

    if ((i > 0)); then
        zenity --error --title="Une erreur est survenue" --text="Une erreur est survenue "
        exit 100
    fi
}</code> ainsi après chaque commande vous pouvez donner des codes d'exécutions différents.

<note tip>Astuce : le plus important dans tout programme est l'algorithme utilisé.</note>
Exemple :
supposons que vous ayez une base de données, avec 3 catégories d'enregistrements possibles :
éléphant bleu, éléphant blanc, éléphant rose ayant chacun 30 individus.
Votre script doit compter le nombre d'éléphants bleus et blancs.
Deux possibilités s'offrent à vous :
  * calculer le nombre d'éléphants bleus + éléphants blancs
ou
  * calculer le nombre total d'éléphants - nombre d'éléphants roses

Quel algorithme choisissez-vous ?

Résultat : le premier car dans le deuxième il faut d'abord calculer le nombre total d'éléphants, donc un calcul en plus :-P .

===== Liens =====

  * (fr) http://marcg.developpez.com/ksh/ : Pour ceux qui souhaitent aller plus loin dans la conception de script shell.
  * (fr) [[http://abs.traduc.org/abs-fr/|Guide avancé d'écriture des scripts Bash]] : Un très bon tutoriel concernant la réalisation du script shell. C'est l'un des plus complets et les mieux détaillés disponibles en français. Il contient également [[http://abs.traduc.org/abs-5.3-fr/apa.html|des exemples de script complets]], une [[http://abs.traduc.org/abs-5.3-fr/apb.html|carte de référence]] (variables, tests...). Ce site est un site qui vaut réellement le détour pour tous ceux qui cherchent à créer des scripts complets en utilisant au mieux les performances du shell.
  * (fr) https://openclassrooms.com/courses/reprenez-le-controle-a-l-aide-de-linux : Un tutoriel très complet pour linux qui comporte quelques parties sur la réalisation de scripts bash.
  * (en) [[http://www.ibm.com/developerworks/library/l-bash-parameters.html|Bash parameters and parameter expansions]]. En anglais mais contient de nombreux exemples concernant la gestion et l'analyse des paramètres.
  * (fr ) [[ftp://ftp-developpez.com/eric-sanchis/IntroProgBash.pdf|Introduction à Bash]]
  * (fr ) [[http://www.scotchlinux.tuxfamily.org/]] exemples de scripts bash, quelques trucs utiles ( fonctions, fonctions comme paramètres... ) 
  * (en) [[https://www.shellcheck.net/]] Permet de corriger la syntaxe du script (parenthèse oubliée, graphie incorrecte d'une commande, un "if" sans son "fi", un "while sans son "do" ou son "done", etc...).

----

//Contributeurs: [[:utilisateurs:Gapz]], [[:utilisateurs:Gloubiboulga]] ,[[:utilisateurs:sparky]] et [[:utilisateurs:deax_one]]//
